#!/usr/bin/python env
# -*- coding: utf-8 -*-
# @Time   :2018/4/26 21:41
# @Author :huchao


import time, datetime, json, requests, random, hashlib
from functools import wraps


class BuMen:
    '''
    布萌区块链pythonsdk

    '''

    # BuMen类的属性
    __client_id = None
    __client_secret = None
    __url = None
    __grant_type = None

    __useList = []  # 用来存储当前创建的用户列表
    __assetlist = []  # 用来存储当前存在的资产列表

    @classmethod
    def getClientSecret(cls):
        return cls.__client_secret

    @classmethod
    def setClassArry(cls, client_id, client_secret, url, grant_type):
        '''初始化类属性'''
        cls.__client_id = client_id
        cls.__client_secret = client_secret
        cls.__url = url
        cls.__grant_type = grant_type

    @classmethod
    def writeLog(cls, filename):
        '''定义一个类方法用来将用户信息,和资产信息写入文件系统中'''
        # 写入文件信息
        with open(filename + "user", "w") as file:
            for user in cls.__useList:
                # Str = (("{}" + "&{}" * (len(user.getUserArryList()) - 1) + "\n").format(*user.getUserArryList()))
                file.write(str(user) + "\n")
        # 写入资产信息
        with open(filename + "asset", "w") as file:
            for asset in cls.__assetlist:
                file.write(str(asset))
                file.write("\n")

    @classmethod
    def readUseInfoInFile(self, filename):
        '''定义一个类方法用来将文件中存储的用户信息恢复成BuMenUser对象'''
        with open(filename + "user", "r") as file:
            while True:
                Str = file.readline()
                if not Str:
                    break
                eval("BuMenUser(" + Str + ")")

        with open(filename + "asset", "r") as file:
            while True:
                Str = file.readline()
                if not Str:
                    break
                L = "BuMenAsset(" + Str.rstrip("\n") + ")"
                eval(L)

    @classmethod
    def getToken(cls):
        '''创建一个BuMen类方法。用来反回一个BuMen 的token'''
        # 生成apiurl
        urlAccessToken = cls.__url + "/oauth2/token"
        # 生成api需要的header
        header = {'Content-Type': 'application/x-www-form-urlencoded'}
        # 生成请求数据
        postData = {'client_id': cls.__client_id, 'client_secret': cls.__client_secret, 'grant_type': cls.__grant_type}
        # ssl._create_default_https_context = ssl._create_unverified_context
        # 通过api获取token
        req = requests.post(urlAccessToken, params=postData, headers=header, verify=False)
        # 返回token字符串
        return req.json()["access_token"]

    @classmethod
    def getTradeNo(self, type):
        '''
        用来获得TradeNo，需要传入要获取TradeNo的类型：类型包括：user；asset
        :param type:
        :return: 返回TradeNo
        '''
        assert type, "Trade_No 类型为空"
        T = int(time.time())
        L = [chr(x) for x in range(65, 65 + 25)] + [chr(x) for x in range(97, 97 + 25)]
        R = "".join(random.sample(L, 10))
        trade_no = type + str(T) + "x" + R
        return trade_no.zfill(32)

    @classmethod
    def getCurrentString(cls):
        '''返回一个随机字符串'''
        Str = [chr(x) for x in range(65, 89)] + [chr(x) for x in range(97, 121)]
        return "".join(random.sample(Str, 32))

    # 操作用户列表
    @classmethod
    def getUserList(cls):
        return cls.__useList

    @classmethod
    def addUsertoList(cls, user):
        cls.__useList.append(user)

    @classmethod
    def removeUserfromList(cls, user):
        cls.__useList.remove(user)

    # 操作资产列表
    @classmethod
    def getAssetList(self):
        return self.__assetlist

    @classmethod
    def addAssetList(cls, Asset):
        cls.__assetlist.append(Asset)

    def getUrl(self):
        return self.__url


class BuMenUser(BuMen):
    '''
    布萌区链量用户类

    用户自己维护的资产列表是一个资产code列表。不具备完全的资产信息。
    如果需要获取完全的资产信息需要依据自身的资产code查询资产类维护的资产列表
    '''
    __slots__ = ["username", "password", "nickname", "metadata", "asscet", "__trade_no", "__bubi_address", "__issure"]

    def __init__(self, username, password, nickname, metadata, asset_code=None, __trade_no=None, __bubi_address=None,
                 __issure=False):
        self.username = username
        self.password = password
        self.nickname = nickname
        self.metadata = metadata
        self.__trade_no = __trade_no
        self.__bubi_address = __bubi_address
        self.__issure = __issure
        self.asset_code = asset_code

        # 创建后的用户加入活跃用户系统中
        if __bubi_address and __trade_no:
            self.addUsertoList(self)

    def __repr__(self):
        # 重写BuMenUser类的__repr__()函数为BuMenUser的构造函数字符串
        s = ("BuMenUser('{}'" + ",'{}'" * (len(self.__slots__) - 1) + ")").format(
            self.username, self.password, self.nickname, self.metadata, self.asset_code, self.__trade_no,
            self.__bubi_address, self.__issure
        )
        return s

    # def __del__(self):
    #     # 用户对象被释放时，同时结束该用户的在线状态
    #     self.removeUserfromList(self)

    def __str__(self):
        '''BuMenUser 对象的字符串属性为该对象的__bubi_address'''
        return self.__repr__().rstrip(")").lstrip("BuMenUser(")

    def __ge__(self, other):
        '''两个BuMenUser对象是否相等取决于两个对象的__bubi_address 属性是否相等'''
        return self.__bubi_address == other.__bubi.address

    def __rge__(self, other):
        return self == other

    def createUser(self):
        '''创建一个新用户'''
        # 生成一个user类型的trade_no
        tradeNo = self.getTradeNo("user")
        # 构建创建用户的请求api数据
        userdate = {
            "user_name": self.username,
            "password": self.password,
            "trade_no": tradeNo,
        }
        # 构建请求api接口
        urlCreateUser = "{}/account/v1/register?access_token={}".format(self.getUrl(), self.getToken())
        # 请求数据
        req = requests.post(urlCreateUser, json=userdate, verify=False)
        # 如果请求错误则返回请求错误信息
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        # 用户创建成功后，更新BuMenUser属性__bubi_address /__trade_no
        self.__bubi_address = req.json()["data"]["bubi_address"]
        self.__trade_no = tradeNo
        self.addUsertoList(self)

    def updatePassword(self, new_password):
        '''重置账户密码'''

        # 构建请求api trade_no
        tradeNo = self.getTradeNo("user")
        # 构建请求api 数据
        data = {
            "user_name": self.username,
            "new_password": new_password,
            "trade_no": tradeNo
        }
        # 构建请求api
        urlresetuser = "{}/account/v1/alterPwd?access_token={}".format(self.getUrl(), self.getToken())
        # 向api请求修改用户密码
        req = requests.post(urlresetuser, json=data, verify=False)
        # 请求如果失败，提取请求失败信息
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        # 请求成功，更新BuMenUser 属性
        self.__bubi_address = req.json()["data"]
        self.password = new_password
        self.__trade_no = tradeNo

    def getUseinfo(self):
        '''返回用户区块链账号详情'''

        # 构建请求api
        urlgetuser = "{}/account/v1/info?bubi_address={}&access_token={}".format(self.getUrl(), self.__bubi_address,
                                                                                 self.getToken())
        # 获得用户账号详情
        req = requests.get(urlgetuser, verify=False)
        # 请求如果失败，提取请求失败信息
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        # 请求成功返回用户资产详情
        return req.json()["data"]

    def getUserIscreate(self):
        '''返回用户注册状态'''

        # 构建请求api
        urlselectuser = "{}/status/account/v1/register?access_token={}&trade_no={}".format(self.getUrl(),
                                                                                           self.getToken(),
                                                                                           self.__trade_no)
        # 请求用户注册状态
        req = requests.get(urlselectuser, verify=False)
        # 如果注册失败返回注册失败信息
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        # 如果注册成功更新用户属性
        self.__issure = True


        # 查询tx信息

    def TXcheck(self, start=0, limit=100):
        '''查询交易信息'''
        url = "{}/account/v1/transaction?bubi_address={}&access_token={}".format(self.getUrl(),
                                                                                 self.__bubi_address,
                                                                                 self.getToken())
        data = {
            "start": start,
            "limit": limit
        }
        req = requests.get(url, json=data, verify=False)
        return req.json()

    def getAsset(self, BuMenAssetCode):
        '''用户资产做增减操作，如果value的值为正则增加相应资产，如果值为负则减少相应资产'''
        # ("reqrqwerqwrq",type(self.asscet))
        # if isinstance(self.asscet, list):
        #     (BuMenAssetCode in self.asscet)
        try:
            # 如果当前用户没有该种类型的资产则直接把该资产加入用户资产列表中
            if not isinstance(self.asscet, list):
                self.asscet = list()
                self.asscet.append(BuMenAssetCode)
            elif BuMenAsset not in self.asscet:
                self.asscet.append(BuMenAssetCode)
        except TypeError as err:
            print("资产参数值非法。请核对", err)

    def sendAsset(self):
        '''用户资产减操作'''

    def getUserArryList(self):
        '''返回布萌用户对象的属性列表'''
        return self.username, self.password, self.nickname, self.metadata, self.asset_code, self.__trade_no, self.__bubi_address

    def getUserBubiAddress(self):
        return self.__bubi_address

    def getUserPassword(self):
        return self.password


class BuMenAsset(BuMen):
    '''
    布萌区块链资产类


    定义：
        1）如果两个资产的asset_name 属性相等则两个资产对象相等.
        3）同种资产做比较运算按照asset_amount属性的值比较，
        4）资产和整型数做比较返回asset_amount属性和整型数之间的比较结果
        5）资产的最小单位是1，不支持浮点运算。计算中出现浮点运算则向下取整
        6）资产运算是asset_amount属性的运算其他属性无关
        7）乘法除法运算只支持和int类型的值计算
    '''

    __slots__ = ["user", "asset_name", 'asset_unit', 'user', 'asset_amount', 'metadata', 'asset_code', 'bc_hash',
                 '__trade_no']

    def __init__(self, asset_name, asset_unit, asset_amount, metadata, user=None, asset_code=None, bc_hash=None,
                 trade_no=None):
        self.user = user
        self.asset_name = asset_name
        self.asset_unit = asset_unit
        self.asset_amount = int(asset_amount)
        self.metadata = metadata
        self.asset_code = asset_code
        self.__trade_no = trade_no
        self.bc_hash = bc_hash
        if user and asset_code and bc_hash and trade_no:
            if isinstance(user, str):
                # 如果是从文件中加载用户数据，则需要把用户的address转变为用户对象
                for use in BuMen.getUserList():
                    if use.getUserBubiAddress() == user:
                        self.user = use
                        break
            self.addAssetList(self)

    def __repr__(self):
        # print(len(self.__slots__),len((self.asset_name,self.asset_unit,self.asset_amount,self.metadata,self.user.getUserBubiAddress(),self.asset_code,self.bc_hash,self.__trade_no)))
        s = ("BuMenAsset('{}'" + ",'{}'" * (len(self.__slots__) - 2) + ")").format(
            self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user.getUserBubiAddress(),
            self.asset_code, self.bc_hash, self.__trade_no)
        # (s)

        return "BuMenAsset(" + s + ")"

    def __str__(self):
        return self.__repr__().lstrip("BuMenAsset(").rstrip(")")

    def __add__(self, other):
        '''两种资产类型相加则返回各自asset_amount属性相加后的结果
            资产类型和整数类型相加则返回asset_amount属性和int值相加后的结果
        '''
        (type(other))
        if isinstance(other, BuMenAsset):
            if self == other:
                self.asset_amount += other.asset_amount
            else:
                raise TypeError
        elif isinstance(other, int):
            self.asset_amount += other
        else:
            raise TypeError
        return BuMenAsset(self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user,
                          self.asset_code, self.bc_hash, self.__trade_no)

    def __radd__(self, other):

        return self + other

    def __mul__(self, other):
        '''资产类型的对象和int值做乘法运算时返回asset_amount属性和int值的乘积'''
        if isinstance(other, int):
            self.asset_amount *= other
        else:
            raise TypeError
        return BuMenAsset(self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user,
                          self.asset_code, self.bc_hash, self.__trade_no)

    def __rmul__(self, other):

        return self * other

    def __mod__(self, other):
        '''资产类型的对象和int值做取余运算时，返回asset_amount属性和int值的余'''
        if isinstance(other, int):
            self.asset_amount %= other
        else:
            raise TypeError
        return BuMenAsset(self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user,
                          self.asset_code, self.bc_hash, self.__trade_no)

    def __rmod__(self, other):
        return self % other

    def __sub__(self, other):
        if isinstance(other, BuMenAsset):
            if self == other:
                self.asset_amount -= int(other.asset_amount)
            else:
                raise TypeError
        elif isinstance(other, int):
            self.asset_amount -= other
        else:
            raise TypeError
        return BuMenAsset(self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user,
                          self.asset_code, self.bc_hash, self.__trade_no)

    def __rsub__(self, other):
        return self - other

    def __divmod__(self, other):
        if isinstance(other, int):
            self.asset_amount //= other
        else:
            raise TypeError
        return BuMenAsset(self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user,
                          self.asset_code, self.bc_hash, self.__trade_no)

    def __rdivmod__(self, other):
        return self // other

    def __eq__(self, other):
        '''两种资产asset_name 属性相等则说两种资产相等
            资产和一个整数值相比，则比较资产的asset_amount属性和int值是否相等
        '''
        if isinstance(other, BuMenAsset):
            return self.asset_code == other.asset_code
        elif isinstance(other, int):
            return int(self.asset_amount) == other
        else:
            raise TypeError

    def __ne__(self, other):
        '''
        两种资产asset_name属性不等则说两种资产不相等
            资产和一个整数值相比，则比较资产的asset_amount属性和int值是否相等
        '''
        if isinstance(other, BuMenAsset):
            return self.asset_code != other.asset_code and int(self.asset_amount) != int(other.asset_amount)
        elif isinstance(other, int):
            return int(self.asset_amount) != other
        else:
            raise TypeError

    def __lt__(self, other):
        if self.__Myisintanche(self, other):
            return int(self.asset_amount) < int(self.asset_amount)
        elif isinstance(other, int):
            return int(self.asset_amount) != other
        else:
            raise TypeError

    def __le__(self, other):
        if self.__Myisintanche(other, self):
            return int(self.asset_amount) <= int(self.asset_amount)
        elif isinstance(other, int):
            return int(self.asset_amount) != other
        else:
            raise TypeError

    def __ge__(self, other):
        if self.__Myisintanche(other, self):
            return int(self.asset_amount) >= int(self.asset_amount)
        elif isinstance(other, int):
            return int(self.asset_amount) >= other
        else:
            raise TypeError

    def __gt__(self, other):
        if self.__Myisintanche(other, self):
            return int(self.asset_amount) > int(other.asset_amount)
        elif isinstance(other, int):
            return int(self.asset_amount) > other
        else:
            raise TypeError

    @staticmethod
    def __Myisintanche(a, b):
        if isinstance(b, BuMenAsset):
            if isinstance(a, b):
                return True
            else:
                return False
        else:
            if isinstance(a, b):
                return True
            else:
                return False

    # 资产发行
    def assetIssue(self, user):
        '''资产发行,同步方式'''
        # 构造自查发行请求url
        urlasset = "{}/asset/v1/issue?access_token={}".format(self.getUrl(), self.getToken())
        # 构造资产发行数据
        trade_no = self.getTradeNo("asset")
        assetdata = {
            "trade_no": trade_no,
            "asset_issuer": user.getUserBubiAddress(),
            "password": user.getUserPassword(),
            "asset_name": self.asset_name,
            "asset_unit": self.asset_unit,
            "asset_amount": self.asset_amount,
            "metadata": self.metadata
        }
        # 发起资产发行请求
        req = requests.post(urlasset, json=assetdata, verify=False, )
        # 验证资产发行请求，请求不成功返回错误信息
        assert not bool(int(req.json()["err_code"])), ",".join(req.json().values())
        # 资产发行成功更新资产和用户信息
        self.bc_hash = req.json()["data"]['bc_hash']
        self.asset_code = req.json()["data"]['asset_code']
        self.user = user
        self.__trade_no = trade_no
        #     把资产添加到资产维护的资产列表中
        self.addAssetList(self)

    # 资产增发
    def assetNewIssue(self, user, asset_amount):
        '''

        :param user: 要发行资产的用户
        :param asset_amount: 增发数量
        :return:
        '''
        if not isinstance(asset_amount, int):
            raise TypeError
            #     构造资产增发请求url
        url = "{}/asset/v1/add2Issue?access_token={}".format(self.getUrl(), self.getToken())

        # 构造资产增发请求数据
        trade_no = self.getTradeNo("asset")
        data = {
            "password": user.getUserPassword(),
            "trade_no": trade_no,
            "asset_code": self.asset_code,
            "asset_amount": asset_amount,
            "metadata": self.metadata
        }
        # 发起资产增发请求
        req = requests.post(url, json=data, verify=False)
        # 验证请求结果，如果不成功返回错误信息
        assert not bool(int(req.json()["err_code"])), ",".join(req.json().values())
        # 更新资产和用户信息
        self.asset_amount += asset_amount
        self.__trade_no = trade_no

    def Transaction(self, userB, amount):
        '''资产从A转移到B'''
        url = "{}/asset/v1/send?access_token={}".format(self.getUrl(), self.getToken())
        trade_no = self.getTradeNo("asset")
        curStr = self.getCurrentString()
        data = {
            "trade_no": trade_no,
            "current_string": curStr,
            "password": self.user.password,
            "asset_code": self.asset_code,
            "asset_amount": amount,
            "from_bubi_address": self.user.getUserBubiAddress(),
            "to_bubi_address": userB.getUserBubiAddress(),
            "metadata": self.metadata,
            "sign": self.__getMd5(amount,curStr, userB, trade_no)  # 签名
        }

        req = requests.post(url, json=data, verify=False)
        assert not int(req.json()["err_code"]), ','.join(req.json().values())
        return req.json()

    # 查询资产发行状态
    def issueStatus(self):
        '''资产发行状态'''
        url = "{}/status/asset/v1/issue?trade_no={}&access_token={}".format(self.getUrl(), self.__trade_no,
                                                                            self.getToken())
        req = requests.get(url, verify=False)
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        return req.json()

    # 查询资产转移状态
    def transactionStatus(self):
        '''资产转移状态查询'''
        url = "{}/status/asset/v1/send?trade_no={}&access_token={}".format(self.getUrl(), self.__trade_no,
                                                                           self.getToken())
        req = requests.get(url, verify=False)
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        return req.json()

    # 查询Trade_no信息
    def trade_noCheck(self):
        '''依据trade_no查询信息'''
        url = ("{}/tx/v1/tradeNo/" + "{}" + "?access_token={}").format(self.getUrl(), self.__trade_no, self.getToken())
        req = requests.get(url, verify=False)
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        return req.json()

    # 查询bc_hash值查询交易信息
    def bc_hashCheck(self):
        '''依据hash值查询交易信息'''
        url = ("{}/tx/v1/" + "{}" + "?access_token={}").format(self.getUrl(), self.bc_hash, self.getToken())
        req = requests.get(url, verify=False)
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        return req.json()

    # 获取资产详情
    def getAssetInfo(self):
        '''获取资产详情'''
        url = "{}/status/asset/v1/send?trade_no={}&access_token={}".format(self.getUrl(), self.__trade_no,
                                                                           self.getToken())
        req = requests.get(url, verify=False)
        assert not int(req.json()["err_code"]), ",".join(req.json().values())
        return req.json()

    def getAssetArryList(self):
        return self.asset_name, self.asset_unit, self.asset_amount, self.metadata, self.user, self.asset_code, self.bc_hash, self.__trade_no

    def __getMd5(self, asset_amount,current_string, touser, trade_no):
        '''
        用来执行md5加密
        :return:
        '''
        stringA = "asset_amount=%s&asset_code=%s&current_string=%s&from_bubi_address=%s&to_bubi_address=%s&trade_no=%s" % (
            asset_amount, self.asset_code, current_string, self.user.getUserBubiAddress(),
            touser.getUserBubiAddress(), trade_no
        )
        stringA = ("%s&key=%s" % (stringA, self.getClientSecret()))
        stringA = stringA.encode(encoding='utf-8')
        print(stringA)
        m2 = hashlib.md5()
        m2.update(stringA)
        return m2.hexdigest()
